package controllers;
import models.Bot_Model;
//import models.Human;
import models.Rules_Model;
//import models.Table;




import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import play.mvc.*;
import play.libs.*;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;
import akka.actor.*;
import static akka.pattern.Patterns.ask;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.util.*;

import javax.swing.JOptionPane;

import models.msgs.*;
import models.msgs.Number;
import static java.util.concurrent.TimeUnit.*;

/**
 * Kontroler gry po stronie serwera.
 * @author Tomasz Kulik, Rafał Dworczak
 *
 */
public class Game_Controller extends UntypedActor
{	
	/**
	 * Ustawienie small i big blind.
	 * @param sb - small blind
	 * @param bb - big blind
	 */
	public static void setBlinds(int sb, int bb)
	{
		rules.setBlinds(sb, bb);
	}
	
	public static void setBal(int x)
	{
		rules.setBal(x);
	}
	
	/**
	 * Zaczyna rozgrywkę.
	 */
	public static void Begin()
	{
		int[] x = new int[1];
		int[] out = rules.ModelControl(x);
		String msgout;
		String tolog;
		String nick = "";
		
		msgout = "40"+out[1]+out[2]+out[3]+out[4];
		
		AddOperation(msgout, 9);
		
		nick = rules.getPlayerNick(out[1]);
		tolog = "Player "+out[1]+" "+nick+" has dealer button.";
		terminal.WriteToLog(tolog);
		nick = rules.getPlayerNick(out[2]);
		tolog = "Player "+out[2]+" "+nick+" has small blind.";
		terminal.WriteToLog(tolog);
		nick = rules.getPlayerNick(out[3]);
		tolog = "Player "+out[3]+" "+nick+" has big blind.";
		terminal.WriteToLog(tolog);
		nick = rules.getPlayerNick(out[4]);
		RoundState();
		tolog = "Player's "+out[4]+" "+nick+" turn.";
		terminal.WriteToLog(tolog);
		Moves(out[4]);
		goid = out[4];
		
		String[] c = new String[4];
				
		for (int i=0; i<player_number; i++)
		{
			c = rules.SendCards(i);
			msgout = "42"+i+c[0]+c[1]+c[2]+c[3]+"0";
			AddOperation( msgout, 9);
		}
		// czeka chwilę.
		msgout = "2"+out[4];
		AddOperation( msgout, 9);
	}
	
	public static void TerminatePlayer(int id)
	{
		rules.DeletePlayer(id);
	}
	
	@Override
    public void onReceive(Object _msg) throws Exception
    {
		ClientMessage temp = (ClientMessage) _msg;
		String input = temp.getMsg();
		int id_client = temp.getId();
		
		String output = "";
		String tolog  = "";
		String msgout = "";
		String nick = "";
		
		switch(input.charAt(0))
		{
			case '1':	// logowanie
				String[] data = input.split(";");
				
				rules.setBlinds(50, 100);
				rules.setBal(1500);
				
				int new_id = rules.CreatePlayer(data[1]);// id przypisane graczowi.
				player_number++;
				
				if ( new_id != -1) // gdy poprawnie przyznano id.
				{
					//client_s.RegisterClient(id_client, new_id);
					//					startaccbal			smallblind			bigblind			id gracza	id;nick;id;nick; wszystkich graczy
					output = "19"+";"+rules.valueOf(0)+";"+rules.valueOf(1)+";"+rules.valueOf(2)+";"+new_id+";"+rules.getPlayersInfo(new_id);
				}
				else;	//error
				
				if ( new_id > 0 )
				{
					tolog = "Player "+new_id+" "+data[1]+" log on.";
					terminal.WriteToLog(tolog);
					
					msgout = "48"+";"+new_id+";"+data[1];
					AddOperation(msgout, new_id);
				}
				else
				{
					tolog = "Player "+new_id+" "+data[1]+" log on.";
					terminal.WriteToLog(tolog);
				}
					//czeka i czeka na kolejnego gracza.
			break;
			case '2':	// licytacja
				int [] msg = new int[3];
				int[] response;
				int x;
				if (goid == id_client && ifch == 0)
				{
					msg[0] = id_client; //czy to jest id gracza?
					x = Character.getNumericValue(input.charAt(1));
					msg[1] = x; //ruch jaki został wykonany podczas licytacji.
					if (x == 0 || x == 3 || x == 4 || x == 5)
						msg[2] = 0;
					else
					{
						data = input.split(";");
						String svalue = data[1];
						msg[2] = Integer.parseInt(svalue); // warto�� dla ruchu.
					}
					
					response = rules.ModelControl(msg);
					
					nick = rules.getPlayerNick(id_client);
					
					if (response[0] != 8) // licytacja przeszďż˝a pomyďż˝lnie
					{
						if (x == 0 || x == 3 || x == 4 || x == 5)
						{
							if (x == 0)
								tolog = "Player "+msg[0]+" "+nick+" has checked.";
							else if (x == 3)
								tolog = "Player "+msg[0]+" "+nick+" has called.";
							else if (x == 4)
								tolog = "Player "+msg[0]+" "+nick+" has folded.";
							else
								tolog = "Player "+msg[0]+" "+nick+" went all in.";
							terminal.WriteToLog(tolog);
							
							msgout = "41"+id_client+x;
						}
						else
						{
							String[] value = input.split(";");
							int p = Integer.parseInt(value[1]);
							if (x == 1)
								tolog = "Player "+msg[0]+" "+nick+" has bet for "+p+".";
							else
								tolog = "Player "+msg[0]+" "+nick+" has raised for "+p+".";
							terminal.WriteToLog(tolog);
							
							msgout = "41"+id_client+x+";"+p;
						}
						AddOperation(msgout, id_client);
						
						//po wysďż˝aniu informacji o licytacji gracza.
						nick = rules.getPlayerNick(response[1]);
						if (response[0] == 2)// nastďż˝pny gracz moďż˝e licytowaďż˝.
						{
							goid = response[1];
							RoundState();
							tolog = "Player's "+response[1]+" "+nick+" turn.";
							terminal.WriteToLog(tolog);
							Moves(response[1]);
							msgout = "2"+response[1];
							AddOperation( msgout, 9);
						}
						else if (response[0] == 3) //przechodzimy do wymiany kart
						{
							tolog = "Cards change phase.";
							terminal.WriteToLog(tolog);
							
							tolog = "Player "+response[1]+" "+nick+" can change cards now.";
							terminal.WriteToLog(tolog);
							
							msgout = "3"+response[1];
							AddOperation( msgout, 9);
							
							ifch = 1;
						}
						else if (response[0] == 6) //koďż˝czymy rundďż˝, sprawdzamy karty.
						{
							tolog = "End of the round.";
							terminal.WriteToLog(tolog);
							RoundState();
							String[] c = new String[4];
							
							for (int i=0; i<player_number; i++)
							{
								c = rules.SendCards(i);
								msgout = "42"+i+c[0]+c[1]+c[2]+c[3]+"0";
								AddOperation( msgout, 9);
							}
							int[] y = new int[1];
							int[] out = rules.ModelControl(y);
							nick = rules.getPlayerNick(out[1]);
							
							tolog = "Player "+out[1]+" "+nick+" has won this round.";
							terminal.WriteToLog(tolog);
							
							msgout = "43"+out[1];
							AddOperation(msgout, 9);
							sleep = true;
							old_time = System.currentTimeMillis();
							
							if (out[2] == 0)
							{	
								tolog = "Player "+out[1]+" "+nick+" has won the game.";
								terminal.WriteToLog(tolog);
								RoundState();
								msgout = "44"+out[1]+"1";
								AddOperation(msgout, 9);
							}
						}
						else if(response[0] == 7) // gdy wszyscy oprďż˝cz jednego sfoldowali.
						{
							String[] c = new String[4];
							
							for (int i=0; i<player_number; i++)
							{
								c = rules.SendCards(i);
								
								msgout = "42"+i+c[0]+c[1]+c[2]+c[3]+"0";
								AddOperation( msgout, 9);
							}
							nick = rules.getPlayerNick(response[1]);
							tolog = "Player "+response[1]+" "+nick+" has won this round.";
							terminal.WriteToLog(tolog);
							RoundState();
							msgout = "43"+response[1]+"";
							AddOperation(msgout, 9);
							sleep = true;
							old_time = System.currentTimeMillis();
						}
						else if (response[0] == 10) //wszyscy gracze sfoldowali.
						{
							msgout = "436";
							AddOperation(msgout, 9);
						}
						output = "46";
					}
					else
						output = "28";
				}
				else if(ifch == 1)
				{
					tolog = "You should change cards now.";
					terminal.WriteToLog(tolog);
				}
				else
				{
					nick = rules.getPlayerNick(goid);
					tolog = "Player's "+goid+" "+nick+" turn.";
					terminal.WriteToLog(tolog);
				}
			break;
			case '3':	// wymiana kart
				msg = new int[6];
				msg[0] = id_client; //czy to jest id gracza?
				x = Character.getNumericValue(input.charAt(1));
				msg[1] = x; // ilo�� zmienionych kart.
				for (int i=2; i<x+2; i++) // do kolejnych pozycji msg[] dopisuje indeksy wymienionych kart.
					msg[i] = Character.getNumericValue(input.charAt(i));
				
				response = rules.ModelControl(msg);
				
				String[] c = new String[4];
				c = rules.SendCards(response[1]);
				output = "42"+response[1]+c[0]+c[1]+c[2]+c[3]+x;
				msgout = "42"+response[1]+"xxxxxxxx"+x;
				AddOperation(msgout, id_client);
				
				nick = rules.getPlayerNick(id_client);
				tolog = "Player "+id_client+" "+nick+" changed "+x+ " cards.";
				terminal.WriteToLog(tolog);
				//after a while.
				nick = rules.getPlayerNick(response[2]);
				if (response[0] == 4)
				{
					tolog = "Player "+response[2]+" "+nick+" can change cards now.";
					terminal.WriteToLog(tolog);
					msgout = "3"+response[2];
					AddOperation(msgout, 9);
				}
				else if (response[0] == 5)
				{
					goid = response[2];
					RoundState();
					tolog = "Player's "+response[2]+" "+nick+" turn.";
					terminal.WriteToLog(tolog);
					Moves(response[2]);
					msgout = "2"+response[2];
					AddOperation(msgout, 9);
					ifch = 0;
				}
			break;
			case '4': // update  // nie bydzie apdejta.
				if(sleep == true)
				{
					if(System.currentTimeMillis() - old_time > 5000)
					{
						int[] k = new int[1];
						int[] out = rules.ModelControl(k);
						
						if (out[0] == 33)
						{
							msgout = "44" + out[1];
						}
						else
						{
							msgout = "40"+out[1]+out[2]+out[3]+out[4];
							
							AddOperation(msgout, 9);
							
							c = new String[4];
									
							for (int i=0; i<player_number; i++)
							{
								c = rules.SendCards(i);
								msgout = "42"+i+c[0]+c[1]+c[2]+c[3];
								AddOperation( msgout, 9);
							}
							msgout = "2"+out[4];
							AddOperation( msgout, 9);
						}
						sleep = false;
					}
				}
				//if(counter_operations[id_client] < last_operations.size())
				//	output = last_operations.get(counter_operations[id_client]++);
				
				//else output = "5";
			break;
			case '9':
				rules.DeletePlayer(id_client);
				String out= "14"+id_client;
				AddOperation(out, id_client);
			break;
		}
		
		getSender().tell(new ClientMessage(output, -1), getSelf());
		
	}
	
	public static void AddOperation(String opr, int id)
	{
		notifyAll(new ClientMessage(opr, id));
		for(int i=0; i<bot_num; i++)
		{
			String result = bots[i].MoveBot(opr);
			if(result != "")
			{
				AddOperation(result, bots[i].getID());
			}
		}
	}
	
	public static void join(final String name, WebSocket.In<JsonNode> in, WebSocket.Out<JsonNode> out ) throws Exception{

        // Send the Join message to the table
        ClientMessage msg = (ClientMessage)Await.result(ask(defaultTable,new ClientMessage("1;"+name, -1), 1000), Duration.create(1, SECONDS));
        String result = msg.getMsg();
        
        if("19".equals(result.substring(0, 2))) {
        	
            ActorRef player =  Akka.system().actorOf(
                    Props.create(Human.class, name, in, out,  defaultTable ));
            
            members.put(name, player);
            player.tell(new ClientMessage(result, -1), Game_Controller.defaultTable);
            player.tell("You have been logged as "+name+"\n", Game_Controller.defaultTable);
            if(player_number >= 3 && begin == false)
            {
            	begin = true;
            	Begin();
            }
        }
    }
	
	public static void AddBot()
	{
		bots[bot_num] = new Bot_Model(rules);
		bots[bot_num].setMyID(rules.CreatePlayer("Bot "+bot_num));
		AddOperation("1;Bot "+bot_num, 9);
		bot_num++;
		player_number++;
	}
	
	public int getPlayerCounter()
	{
		return player_number;
	}
	
	static public void RoundState()
	{
		String nick = "";
		String tolog = "Max bet: "+rules.max_bet+".";
		terminal.WriteToLog(tolog);
		for (int i=0; i<3; i++)
		{
			nick = rules.getPlayerNick(i);
			tolog = "Player's "+i+" "+nick+" account balance: "+rules.player[i].getAccBal()+". Inpot: "+rules.player[i].getInpot()+".";
			terminal.WriteToLog(tolog);
		}
	}
		
	static public void Moves(int id)
	{
		String tolog = "Available moves: ";
		int move = rules.AvalibleMoves(id);
		if ((move & rules.check) == rules.check)
		{
			tolog += "check; ";
		}
		if ((move & rules.bet) == rules.bet)
		{
			tolog += "bet; ";
		}
		if ((move & rules.raise) == rules.raise)
		{
			tolog += "raise; ";
		}
		if ((move & rules.call) == rules.call)
		{
			tolog += "call; ";
		}
		if ((move & rules.fold) == rules.fold)
		{
			tolog += "fold; ";
		}
		if ((move & rules.all_in) == rules.all_in)
		{
			tolog += "all-in; ";
		}
		
		terminal.WriteToLog(tolog);
	}
	
	static public void notifyAll(Object msg)
    {
        for (ActorRef member : members.values())
        {
            member.tell(msg, defaultTable);
        }
    }
	
	// Default table.
    static ActorRef defaultTable = Akka.system().actorOf(Props.create(Game_Controller.class));
    
    // Members of this table.
    static Map<String, ActorRef> members = new HashMap<String, ActorRef>();
	
	private static boolean sleep = false;
	private static long old_time = 0;
	private static int ifch = 0;
	private static int goid = 0;
	private static int bot_num = 0;
	private static Bot_Model[] bots = new Bot_Model[6];
	private static Terminal terminal = new Terminal();
	private static int player_number=0;
	private static Rules_Model rules = new Rules_Model();
	private static boolean begin = false;
	
}
